在純 JavaScript 中沒有支援「元組」的寫法,不過其實在有些程式語言中是有的,如 Python, Rust, C#
本篇將來帶你了解「元組」基本概念,並且透過情境範例去了解其特性和須注意之處
元組(Tuple)是陣列的一種,在處理特定數量和型別的資料時很好用,它可以明確點出哪個位置要對應哪種型別,是有序性
的
以下利用幾個情境範例來說明:
語法:中括號裡包住 type,每個 type 用逗號隔開,例如
[string, string, number]
person
是一個元組,預設只接受三個值,型別分別是 string
, string
, number
如果在特定位置給予不符合型別的值會報錯
多寫了沒定義的值也會報錯
let person: [string, string, number];
person = ['Hannah', 'beautiful', 18]; // ✅ pass
person = ['Hannah', '18', 'beautiful']; // ❌ Type 'string' is not assignable to type 'number'
person = ['Hannah', 18, 'beautiful', '123']; // ❌ Source has 4 element(s) but target allows only 3
我們可以在 Tuple 上使用陣列處理方法,但需要注意某些操作可能會破壞 Tuple 的型別結構,例如:map()
, push()/pop()
, shift()/unshift()
, splice()
等, 導致 TypeScript 編譯器報錯
let person: [string, string, number];
person.push('super beautiful'); // ❓ 猜猜看 push 後的結果
person.push(true); // ❓ 猜猜看 push 後的結果
答案是...
// 前面略
person.push('super beautiful'); // ✅ Pass
person.push(true); // ❌ Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
person.push('super beautiful');
居然沒報錯,不是只有定義三個值的型別嗎?
原因是索引 3+ 之後的值,就算沒別定義,型別一樣會被限制為元組中每個型別的聯合型別
,以此範例來說就是 string | number
這也是為什麼塞入 string 時不會報錯,而下一行 person.push(true);
會報錯的原因
元組可以透過 ?
來表示可選屬性,這是它的專屬福利,相反的陣列就無法直接這樣做
但要注意的是可選屬性只能出現在「末尾」,且會影響元組長度
[string, number, boolean?]
的長度可能為 2 或 3let tuple: [string, number, boolean?];
tuple = ["hello", 42]; // ✅ Pass
tuple = ["hello", 42, true]; // ✅ Pass
// ❌ 下面會報錯
// tuple = ["hello"]; // 第二個元素(number)是必須的
// tuple = ["hello", true]; // 第二個元素的型別不匹配
Readonly
來保護你的元組如果你想要保護你的元組不被修改,可以在最前面加上 readonly
關鍵字
// Readonly tuple
const readonlyTuple: readonly [number, boolean, string] = [5, true, 'test'];
readonlyTuple.push('hello'); // ❌ Property 'push' does not exist on type 'readonly [number, boolean, string]'.
readonlyTuple[0] *= 5; // ❌ Cannot assign to '0' because it is a read-only property.
JavaScript 對陣列的解構方法也可以拿來用在元組上
let tuple1: [number, string, boolean] = [1, "hello", true];
let [a, b, c] = tuple1;
console.log(a); // 1
console.log(b); // "hello"
console.log(c); // true
let tuple2: [number, string, boolean] = [1, "hello", true];
let [x, , z] = tuple2;
console.log(x); // 1
console.log(z); // true
...
let tuple3: [number, string, boolean, number] = [1, "hello", true, 2024];
let [first, second, ...rest] = tuple3; // 解構前兩個元素,將剩餘元素放入另一個陣列
console.log(first); // 1
console.log(second); // "hello"
console.log(rest); // [true, 2024]
undefined
。給予初始值
或條件檢查
是一種解法let tuple4: [string, number?, boolean?] = ["hello"];
let [p1, p2 = 0, p3 = false] = tuple4; // 解構可選元素,並給予初始值
console.log(a); // "hello"
console.log(b); // 0
console.log(c); // false
p2
、p3
會得到 undefined
let tuple4: [string, number?, boolean?] = ["hello"];
let [p1, p2, p3] = tuple4;
每天講的內容有推到 github 上喔
在實踐中,既然都選擇使用 tuple 了,那最好是保持 Tuple 的不變性,以避免型別相關的錯誤
屬性 | 陣列 | 元組 |
---|---|---|
特性 | 通常包含單一類型的多個元素 | 可以包含不同類型的多個元素 |
範例 | string[] , Array<string> |
[string, number] |
使用時機 | 同質性高的資料集合 | 具有固定數量和多種型別的資料結構 |
靈活性 | 高 | 低(結構固定) |
記憶體使用和存取速度 | 受動態操作影響 | 元組數量固定的特性,有助於 JavaScript V8 引擎進行一些優化,但是十分微小 |